
<!DOCTYPE html>
<html lang="en" class="loading">
<head><meta name="generator" content="Hexo 3.9.0">
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Netty学习-java NIO零拷贝 - kyralo</title>
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="google" content="notranslate">
    <meta name="keywords" content="kyralo,"> 
    <meta name="description" content="零拷贝基本介绍
    
        CPU不执行拷贝数据从一个存储区域到另一个存储区域的任务，这通常用于通过网络传输一个文件时以减少CPU周期和内存带宽(零拷贝是从操作系统的角度来考虑, 是不,"> 
    <meta name="author" content="kyralo"> 
    
    <link rel="icon" href="https://cdn.jsdelivr.net/gh/kyralo/cdn/ico/favicon.ico"> 
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyralo/cdn@v1.0.2/css/diaspora.css">
    <!-- <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> -->
    <script>
         (adsbygoogle = window.adsbygoogle || []).push({
              google_ad_client: "ca-pub-8691406134231910",
              enable_page_level_ads: true
         });
    </script>
    <!-- <script async custom-element="amp-auto-ads"
        src="https://cdn.ampproject.org/v0/amp-auto-ads-0.1.js">
    </script> -->
</head>
</html>
<body class="loading">
    <span id="config-title" style="display:none">kyralo</span>
    <div id="loader"></div>
    <div id="single">
    <div id="top" style="display: block;">
    <div class="bar" style="width: 0;"></div>
    <a class="icon-home image-icon" href="javascript:;" data-url="https://kyralo.github.io"></a>
    <!-- <div title="播放/暂停" class="icon-play"></div> -->
    <h3 class="subtitle">Netty学习-java NIO零拷贝</h3>
    <div class="social">
        <div>
            <div class="share">
                <a title="获取二维码" class="icon-scan" href="javascript:;"></a>
            </div>
            <div id="qr"></div>
        </div>
    </div>
    <div class="scrollbar"></div>
</div>

    <div class="section">
        <div class="article">
    <div class='main'>
        <h1 class="title">Netty学习-java NIO零拷贝</h1>
        <div class="stuff">
            <span>二月 18, 2020</span>
            
  <ul class="post-tags-list"><li class="post-tags-list-item"><a class="post-tags-list-link" href="/tags/IO/">IO</a></li><li class="post-tags-list-item"><a class="post-tags-list-link" href="/tags/Java/">Java</a></li><li class="post-tags-list-item"><a class="post-tags-list-link" href="/tags/NIO/">NIO</a></li><li class="post-tags-list-item"><a class="post-tags-list-link" href="/tags/零拷贝/">零拷贝</a></li></ul>


        </div>
        <div class="content markdown">
            <h4 id="零拷贝基本介绍"><a href="#零拷贝基本介绍" class="headerlink" title="零拷贝基本介绍"></a>零拷贝基本介绍</h4><ol>
    <li>
        CPU不执行拷贝数据从一个存储区域到另一个存储区域的任务，这通常用于通过网络传输一个文件时以减少CPU周期和内存带宽(零拷贝是从操作系统的角度来考虑, <span style="color: red">是不经过cpu拷贝, 但是DMA拷贝(直接内存映射)</span>不可避免)
    </li>
    <li>
        在java程序中, 常用的零拷贝有 mmap(内存映射) 和 sendFile.
    </li>
    <li>
        我们说的零拷贝是从操作系统的角度来说的.因为内核缓冲区之间没有数据是重复的(只有kernel buffer有一份数据).
    </li>
</ol>


<h4 id="零拷贝的优点"><a href="#零拷贝的优点" class="headerlink" title="零拷贝的优点"></a>零拷贝的优点</h4><ul>
    <li>
        减少甚至完全避免不必要的CPU拷贝，从而让CPU解脱出来去执行其他的任务
    </li>
    <li>
        减少内存带宽的占用
    </li>
    <li>
        通常零拷贝技术还能够减少用户空间和操作系统内核空间之间的上下文切换
    </li>
</ul>

<h4 id="传统io读写方式"><a href="#传统io读写方式" class="headerlink" title="传统io读写方式"></a>传统io读写方式</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">File file = <span class="keyword">new</span> File(<span class="string">"index.html"</span>);</span><br><span class="line">RandomAccessFile raf = <span class="keyword">new</span> RandomAccessFile(file, <span class="string">"rw"</span>);</span><br><span class="line"><span class="keyword">byte</span>[] arr = <span class="keyword">new</span> <span class="keyword">byte</span>[(<span class="keyword">int</span>) file.length()];</span><br><span class="line">raf.read(arr);</span><br><span class="line">Socket socket = <span class="keyword">new</span> ServerSocket(<span class="number">8080</span>).accept();</span><br><span class="line">socket.getOutputStream().write(arr);</span><br></pre></td></tr></table></figure>

<h5 id="图示"><a href="#图示" class="headerlink" title="图示"></a>图示</h5><p><img src="https://view.2heng.xin/images/2020/02/18/io.png" alt></p>
<p style="color: blue">
    <span>相关说明: </span><br>
    <ul>
        <li>
            用户态: use context
        </li>
        <li>
            内核态: kernel context
        </li>
        <li>
            DMA copy: 直接内存复制(数据) 
        </li>
        <li>
            CPU copy: 通过cpu进行数据复制
        </li>
        <li>
            User space: 用户态空间
        </li>
        <li>
            Kernel space: 内核态空间
        </li>
    </ul>
</p>
<br>
<p style="color: red">
    如图传统io的数据读写过程经历了4次拷贝(2次DMA拷贝, 2次CPU拷贝),
    4次上下文切换
</p>

<h4 id="使用mmap读写方式"><a href="#使用mmap读写方式" class="headerlink" title="使用mmap读写方式"></a>使用mmap读写方式</h4><p>
    通过内存映射，将文件映射到内核缓冲区，同时，用户空间可以共享内核空间的数据。这样，在进行网络传输时，就可以减少内核空间到用户控件的拷贝次数
</p>

<h5 id="图示-1"><a href="#图示-1" class="headerlink" title="图示"></a>图示</h5><p><img src="https://view.2heng.xin/images/2020/02/18/mmap.png" alt></p>
<p style="color: red">
    如图, 使用mmap读写方式经历了3次拷贝(2次DMA拷贝, 1次CPU拷贝),
    4次上下文切换.对比传统io的读写, mmap方式减少了在 user buffer ->socket buffer
    这个地方的CPU拷贝
</p>

<h4 id="sendfile"><a href="#sendfile" class="headerlink" title="sendfile"></a>sendfile</h4><h5 id="Linux-2-1版的sendfile函数"><a href="#Linux-2-1版的sendfile函数" class="headerlink" title="Linux 2.1版的sendfile函数"></a>Linux 2.1版的sendfile函数</h5><p>
   数据根本不经过用户态，直接从内核缓冲区进入到 Socket Buffer，同时，由于和用户态完全无关，就减少了一次上下文切换。 
</p>

<p><img src="https://view.2heng.xin/images/2020/02/18/sendfile_2.1.png" alt></p>
<p style="color: red">
    如图, Linux 2.1版的sendfile方式经历了3次拷贝(2次DMA拷贝, 1次CPU拷贝),
    3次上下文切换. 对比mmap方式, 减少了一次用户态到内核态的切换
</p>

<h5 id="Linux-2-4版的sendfile函数"><a href="#Linux-2-4版的sendfile函数" class="headerlink" title="Linux 2.4版的sendfile函数"></a>Linux 2.4版的sendfile函数</h5><p>
    Linux 在 2.4 版本中，做了一些修改，避免了从内核缓冲区拷贝到 Socket buffer 的操作，直接拷贝到协议栈，从而再一次减少了数据拷贝。
</p>

<p><img src="https://view.2heng.xin/images/2020/02/18/sendfile_2.4.png" alt></p>
<p style="color: red">
    如图, Linux 2.4版的sendfile方式只经历了2次DMA拷贝, 3次上下文切换,
    组织实现了零拷贝(无CPU拷贝)
</p>

<h4 id="mmap-和-sendfile-的区别"><a href="#mmap-和-sendfile-的区别" class="headerlink" title="mmap 和 sendfile 的区别"></a>mmap 和 sendfile 的区别</h4><ol>
    <li>
        mmap适合小数据量读写, sendfile适合大文件传输.
    </li>
    <li>
        mmap需要4次上下文切换, 3次数据拷贝<br>
        sendflie 需要3次上下文切换, 最少2次数据拷贝(因linux内核版本不同)
    </li>
    <li>
        sendfile可以利用DMA方式减少CPU拷贝, 而mmap必须使用内核拷贝到Socket缓冲区的方式
    </li>
</ol>

<h4 id="NIO零拷贝实例"><a href="#NIO零拷贝实例" class="headerlink" title="NIO零拷贝实例"></a>NIO零拷贝实例</h4><p>
    <ol>
        <li>使用NIO零拷贝方式传递一个大文件</li>
    </ol>
</p>

<p>Server端代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NioZeroCopyServer</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line"></span><br><span class="line">        InetSocketAddress address = <span class="keyword">new</span> InetSocketAddress(<span class="number">8888</span>);</span><br><span class="line">        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();</span><br><span class="line">        ServerSocket serverSocket = serverSocketChannel.socket();</span><br><span class="line">        serverSocket.bind(address);</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* 创建buffer */</span></span><br><span class="line">        ByteBuffer byteBuffer = ByteBuffer.allocate(<span class="number">4096</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line"></span><br><span class="line">            SocketChannel socketChannel = serverSocketChannel.accept();</span><br><span class="line">            <span class="keyword">int</span> readCount = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">while</span> (-<span class="number">1</span> != readCount) &#123;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    readCount = socketChannel.read(byteBuffer);</span><br><span class="line">                    <span class="keyword">if</span> (readCount != <span class="number">0</span>) &#123;</span><br><span class="line">                        System.out.println(readCount);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">/* 倒带 position = 0 mark作废 */</span></span><br><span class="line">                byteBuffer.rewind();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<hr>

<p>Client端代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NioZeroCopyClient</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception</span>&#123;</span><br><span class="line">        SocketChannel socketChannel = SocketChannel.open();</span><br><span class="line">        socketChannel.connect(<span class="keyword">new</span> InetSocketAddress(<span class="string">"localhost"</span>, <span class="number">8888</span>));</span><br><span class="line">        String fileName = <span class="string">"xxx.zip"</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* 获取一个文件 channel */</span></span><br><span class="line">        FileChannel fileChannel = <span class="keyword">new</span> FileInputStream(fileName).getChannel();</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* 准备发送 */</span></span><br><span class="line">        <span class="keyword">long</span> startTime = System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">            1.linux下一个transferTo方法就可以完成传输,</span></span><br><span class="line"><span class="comment">            在Windows下一次调用transferTo方法最多发送的数据大小有限制(最多8M), 需要分段传输.</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">            2.transferTo底层使用的是零拷贝</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">long</span> position = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">long</span> size = fileChannel.size();</span><br><span class="line">        <span class="keyword">long</span> total = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (position &lt; size) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">long</span> transfer = fileChannel.transferTo(position, fileChannel.size(), socketChannel);</span><br><span class="line">            System.out.println(<span class="string">"发送："</span> + transfer);</span><br><span class="line">            <span class="keyword">if</span> (transfer &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            total += transfer;</span><br><span class="line">            position += transfer;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">            linux下直接加入以下代码即可</span></span><br><span class="line"><span class="comment">            long total = fileChannel.transferTo(0, fileChannel.size(), socketChannel)</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        System.out.println(<span class="string">"发送的总字节数: = "</span> + total + <span class="string">"耗时: "</span> + (System.currentTimeMillis() - startTime));</span><br><span class="line">        <span class="comment">/* 关闭 */</span></span><br><span class="line">        fileChannel.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>
    结果:
    </p><p>
        xxx.zip 文件大小: 13.4 MB (14,151,048 字节) 
    </p>
    <p>
       Client:
        发送：8388608
        发送：5762440
        发送的总字节数: = 14151048耗时: 80
    </p>
    <p>
        Server: 
        (比较长, 截取最后部分) 
        ... 
        4096
        4096
        4096
        4096
        4096
        4096
        4096
        3484
    </p>
<p></p>
        </div>
        
    <div id='gitalk-container' class="comment link"
        data-ae='false'
        data-ci='eeec43124e53633945c0'
        data-cs='e2332cffb30ae24f185c1701286958cddd748ffb'
        data-r='kyralo.github.io'
        data-o='kyralo'
        data-a='kyralo'
        data-d='false'
    >查看评论</div>


    </div>
    
        <div class='side'>
            <ol class="toc"><li class="toc-item toc-level-4"><a class="toc-link" href="#零拷贝基本介绍"><span class="toc-number">1.</span> <span class="toc-text">零拷贝基本介绍</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#零拷贝的优点"><span class="toc-number">2.</span> <span class="toc-text">零拷贝的优点</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#传统io读写方式"><span class="toc-number">3.</span> <span class="toc-text">传统io读写方式</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#图示"><span class="toc-number">3.1.</span> <span class="toc-text">图示</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#使用mmap读写方式"><span class="toc-number">4.</span> <span class="toc-text">使用mmap读写方式</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#图示-1"><span class="toc-number">4.1.</span> <span class="toc-text">图示</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#sendfile"><span class="toc-number">5.</span> <span class="toc-text">sendfile</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#Linux-2-1版的sendfile函数"><span class="toc-number">5.1.</span> <span class="toc-text">Linux 2.1版的sendfile函数</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#Linux-2-4版的sendfile函数"><span class="toc-number">5.2.</span> <span class="toc-text">Linux 2.4版的sendfile函数</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#mmap-和-sendfile-的区别"><span class="toc-number">6.</span> <span class="toc-text">mmap 和 sendfile 的区别</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#NIO零拷贝实例"><span class="toc-number">7.</span> <span class="toc-text">NIO零拷贝实例</span></a></li></ol>
        </div>
    
</div>


    </div>
</div>
</body>
<script src="//cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>
<script src="//lib.baomitu.com/jquery/1.8.3/jquery.min.js"></script>
<script src="//cdn.jsdelivr.net/gh/kyralo/cdn/js/plugin.js"></script>
<script src="//cdn.jsdelivr.net/gh/kyralo/cdn/js/diaspora.js"></script>
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/kyralo/cdn/css/photoswipe.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/kyralo/cdn/css/default-skin.css">
<script src="//cdn.jsdelivr.net/gh/kyralo/cdn/js/photoswipe.min.js"></script>
<script src="//cdn.jsdelivr.net/gh/kyralo/cdn/js/photoswipe-ui-default.min.js"></script>

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>
    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">
        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>
        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">
            <div class="pswp__top-bar">
                <!--  Controls are self-explanatory. Order can be changed. -->
                <div class="pswp__counter"></div>
                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
                <button class="pswp__button pswp__button--share" title="Share"></button>
                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                      <div class="pswp__preloader__cut">
                        <div class="pswp__preloader__donut"></div>
                      </div>
                    </div>
                </div>
            </div>
            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div> 
            </div>
            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>
            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>
            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>
        </div>
    </div>
</div>



<!-- Google Analytics -->
<script type="text/javascript">
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-146789315-1', 'auto');
  ga('send', 'pageview');
</script>
<!-- End Google Analytics -->


<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer@1.10/dist/APlayer.min.css">
<script src="https://cdn.jsdelivr.net/npm/aplayer@1.10/dist/APlayer.min.js"></script>
<style>
  .aplayer .aplayer-lrc {
    height: 35px;
  }
  .aplayer .aplayer-lrc p{
    font-size: 16px;
    font-weight: 700;
    line-height: 18px !important;
  }
  .aplayer .aplayer-lrc p.aplayer-lrc-current{
    color: #FF1493;
  }
  .aplayer.aplayer-narrow .aplayer-body{
    left: -66px !important;
  }
  .aplayer.aplayer-fixed .aplayer-lrc {
    display: none;
  }
  .aplayer .aplayer-lrc.aplayer-lrc-hide {
      display:none !important;
  }
  .aplayer.aplayer-fixed .lrc-show {
    display: block;
    background: rgba(255, 255, 255, 0.8);
  }
</style>
<div class="aplayer"

    data-id="2725346449"

    data-server="netease"

    data-type="playlist"

    data-fixed="true"

    data-autoplay="false"

    data-loop="all"

    data-order="random"

    data-preload="auto"

    data-volume="0.7"

    data-mutex="true"

</div>
<script src="https://cdn.jsdelivr.net/npm/meting@1.2/dist/Meting.min.js"></script>
<script>
  $(function(){
    $('body').on('click', '.aplayer', function(){
      if($('.aplayer-button').hasClass('aplayer-play')) {
        $('.aplayer-lrc').removeClass('lrc-show');
      } else {
        $('.aplayer-lrc').addClass('lrc-show');
      }
    })
  });
</script>
</html>
